﻿using Acl.ComponentModel;
using Acl.ComponentModel.Annotions;
using Acl.Reflection;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Acl.Modular
{
    /// <summary>
    /// 模块管理器
    /// </summary>
    [Component]
    class ModuleManager : BooleanDisposable, IModuleManager
    {
        private readonly IServiceContainer _services;
        private readonly IModuleActivaor[] _activators;
        private readonly List<Module> _modules = new List<Module>();
        private readonly SystemActivator _systemActivator;
        private readonly ModuleListenerManager _eventManager;
        private readonly uint _moduleIdSeed = 0;

        /// <summary>
        /// 构造函数
        /// </summary>
        public ModuleManager(
            IServiceContainer services,
            [InjectMany]
            Lazy<IModuleActivaor, IModuleDescriptor>[] activators = null,
            [InjectMany]
            params IModuleListener[] listeners)
        {
            _services = services;
            _eventManager = new ModuleListenerManager(listeners);

            var version = Assembly.GetExecutingAssembly().GetAttribute<AssemblyVersionAttribute>(false);
            var module = new Module(_moduleIdSeed, "system.module", "", 0, version?.Version);
            _modules.Add(module);
            _systemActivator = new SystemActivator(module, this);

            if (activators != null)
            {
                foreach (var item in activators)
                {
                    _moduleIdSeed++;
                    module = new Module(item.Metadata, _moduleIdSeed);
                    _modules.Add(module);
                    if (item.Value is ModuleActivator activator)
                    {
                        activator.Module = module;
                    }
                    else
                    {
                        Acl.Linq.Expressor<IModuleActivaor>.Property(t => t.Module).Set(item.Value, module);
                    }
                }

                _activators = activators
                    .OrderBy(p => p.Metadata.StartLevel)
                    .Select(p => p.Value)
                    .ToArray();
            }
            else
            {
                _activators = new IModuleActivaor[0];
            }

            _modules = _modules.OrderBy(p => p.StartLevel).ThenByDescending(p => p.IsSystemModule).ToList();

            //installed event
            var e = new ModuleEventArgs();
            for (var i = 1; i < _modules.Count; i++)
            {
                e.Module = _modules[i];
                _eventManager.OnModuleChanged(e);
            }

            e.Module = _modules[0];
            _eventManager.OnModuleChanged(e);
        }

        class SystemActivator : IModuleActivaor
        {
            private readonly ModuleManager _moduleManager;
            private readonly Module _module;
            public IModule Module => _module;

            public SystemActivator(Module module, ModuleManager moduleManager)
            {
                _moduleManager = moduleManager;
                _module = module;
            }

            public void Configure(ModuleContext ctx)
            {
                if (_module.State != ModuleState.Installed)
                    throw new ModuleException(_module, $"Module:'{_module.Name}' cannot be configue, since it is {_module.State}.");

                var e = new ModuleEventArgs { Module = _module };
                _module.State = ModuleState.Configuring;
                _moduleManager._eventManager.OnModuleChanged(e);

                foreach (var activator in _moduleManager._activators)
                {
                    e.Module = activator.Module;
                    var module = e.Module as Module;
                    if (module.State != ModuleState.Installed)
                        throw new ModuleException(module, $"Module:'{module.Name}' cannot be configue, since it is {module.State}.");

                    module.State = ModuleState.Configuring;
                    _moduleManager._eventManager.OnModuleChanged(e);

                    ctx.Module = module;
                    try
                    {
                        activator.Configure(ctx);
                    }
                    catch
                    {
                        throw;
                    }

                    module.State = ModuleState.Configured;
                    _moduleManager._eventManager.OnModuleChanged(e);
                }

                e.Module = _module;
                _module.State = ModuleState.Configured;
                _moduleManager._eventManager.OnModuleChanged(e);
            }

            public bool Match(ModuleContext context) => true;

            public void Start(ModuleContext ctx)
            {
                if (_module.State != ModuleState.Configured)
                    throw new ModuleException(_module, $"Module:'{_module.Name}' cannot be start, since it is {_module.State}.");

                var e = new ModuleEventArgs { Module = _module };
                _module.State = ModuleState.Starting;
                _moduleManager._eventManager.OnModuleChanged(e);

                foreach (var activator in _moduleManager._activators)
                {
                    e.Module = activator.Module;
                    ctx.Module = activator.Module;

                    if (activator.Match(ctx))
                    {
                        var module = activator.Module as Module;
                        if (module.State != ModuleState.Configured)
                            throw new ModuleException(module, $"Module:'{module.Name}' cannot be start, since it is {module.State}.");

                        module.State = ModuleState.Starting;
                        _moduleManager._eventManager.OnModuleChanged(e);

                        activator.Start(ctx);

                        module.State = ModuleState.Started;
                        _moduleManager._eventManager.OnModuleChanged(e);
                    }
                }

                _module.State = ModuleState.Started;
                e.Module = _module;
                _moduleManager._eventManager.OnModuleChanged(e);
            }

            public void Stop(ModuleContext ctx)
            {
                if (_module.State != ModuleState.Configured && _module.State != ModuleState.Started)
                    throw new ModuleException(_module, $"Module:'{_module.Name}' cannot be stop, since it is {_module.State}.");

                var e = new ModuleEventArgs { Module = _module };
                _module.State = ModuleState.Stopping;
                _moduleManager._eventManager.OnModuleChanged(e);

                foreach (var activator in _moduleManager._activators.Reverse())
                {
                    e.Module = activator.Module;
                    ctx.Module = activator.Module;

                    if (activator.Match(ctx))
                    {
                        var module = activator.Module as Module;
                        if (module.State != ModuleState.Configured && module.State != ModuleState.Started)
                            throw new ModuleException(module, $"Module:'{module.Name}' cannot be stop, since it is {module.State}.");

                        module.State = ModuleState.Stopping;
                        _moduleManager._eventManager.OnModuleChanged(e);

                        activator.Stop(ctx);

                        module.State = ModuleState.Stopped;
                        _moduleManager._eventManager.OnModuleChanged(e);
                    }
                }

                _module.State = ModuleState.Stopped;
                e.Module = _module;
                _moduleManager._eventManager.OnModuleChanged(e);
            }
        }

        /// <summary>
        /// ioc容器
        /// </summary>
        public virtual IServiceProvider ServiceProvider { get; protected set; }

        public IModule[] Modules { get { return _modules.ToArray(); } }

        /// <inheritdoc/>
        public virtual void Configure()
        {
            var ctx = new ModuleContext(_services);
            _systemActivator.Configure(ctx);
        }

        /// <inheritdoc/>
        public virtual void Start()
        {
            var ctx = new ModuleContext(_services);
            _systemActivator.Start(ctx);
        }

        /// <inheritdoc/>
        public virtual void Stop()
        {
            var ctx = new ModuleContext(_services);
            _systemActivator.Stop(ctx);
        }

        /// <inheritdoc/>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                switch (_systemActivator.Module.State)
                {
                    case ModuleState.Configured:
                    case ModuleState.Started:
                        Stop();
                        break;
                }
            }
        }
    }
}